/*
 * Copyright 2014, NuzoweR@Schneefeuer <nuzower@gmail.com>.
 * The Settlers Online - Combat Simulator (tsosim).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Statistics.h"
#include "Utils.h"

#include <iomanip>
#include <stdexcept>
#include <cstring>

void Statistics::ComputeAverage()
{
	if(rounds_data.number_iterations != rounds_data.iteration_results.size()) {
		throw std::runtime_error("inconclusive statistics");
	}

	/*
	 * Units
	 */

	for(unsigned int i = 0; i < unit_data.size(); ++i) 
	{
		Data& data = unit_data[i];
		data.results.average = 0;

		if(data.number_iterations != data.iteration_results.size() || data.number_iterations != rounds_data.number_iterations) {
			throw std::runtime_error("inconclusive statistics");
		}

		int smin = data.start_number;
		int smax = 0;

		std::list<int>::const_iterator cit = data.iteration_results.begin();
		for(; cit != data.iteration_results.end(); ++cit)
		{
			data.results.average += *cit;
			if(*cit > smax) smax = *cit;
			if(*cit < smin) smin = *cit;
		}
		data.results.average /= data.number_iterations;

		data.results.stat_min = smin;
		data.results.stat_max = smax;
	}


	/*
	 * Rounds
	 */

	{
		int smin = rounds_data.number_iterations;
		int smax = 0;

		rounds_data.results.average = 0;
		
		std::list<int>::const_iterator cit = rounds_data.iteration_results.begin();
		for(; cit != rounds_data.iteration_results.end(); ++cit)
		{
			rounds_data.results.average += *cit;

			if(*cit > smax) smax = *cit;
			if(*cit < smin) smin = *cit;
		}

		rounds_data.results.average /= rounds_data.number_iterations;

		rounds_data.results.stat_min = smin;
		rounds_data.results.stat_max = smax;

	}
}

static const char* multinum(int num) {
	switch(num) {
	case 0 : return "";
	case 1 : return "#";
	case 2 : return "##";
	case 3 : return "###";
	case 4 : return "####";
	case 5 : return "#####";
	case 6 : return "######";
	default : 
		return "";
	};
	return "";
}

std::string str_a_of_b(int a, int b, int num_width) {
	static std::stringstream ss;
	ss.str("");
	ss << std::setw(num_width) << a << "/" << std::setw(num_width) << b;
	return ss.str();
}

std::string str_a_of_b_fl(float a, int b, int num_width) {
	static std::stringstream ss;
	ss.str("");
	ss.precision(2);
	ss << std::setw(num_width) << std::fixed << a << "/" << std::setw(num_width) << b;
	return ss.str();
}

static void print_result_number(std::ostream& out, int value, int total_value, int width_a_of_b, int width_total, int width_lost)
{
	out << " | " << std::setw(width_a_of_b)  << str_a_of_b(value, total_value, width_total);
	if(value - total_value < 0)
	{
		out << " (" << std::setw(width_lost+1) << (value - total_value) << ")";
	}
	else if(width_lost > 0)
	{
		out << std::setw(width_lost+4) << "";
	}
}

static void print_result_number_fl(std::ostream& out, float value, int total_value, int width_a_of_b, int width_total, int width_lost)
{
	out << " | " << std::setw(width_a_of_b+1)  << str_a_of_b_fl(value, total_value, width_total);
	if(value - total_value < 0)
	{
		out.precision(2);
		out << " (" << std::setw(width_lost+4) << std::fixed << (value - total_value) << ")";
	}
	else if(width_lost > 0)
	{
		out << std::setw(width_lost+7) << "";
	}
}


void Statistics::Print(std::ostream& out, bool show_minmax)
{
	struct Width {
		int name, units,
			minmax,
			maxlost,
			average;
	} WIDTH, FULLWIDTH;

	memset(&WIDTH, 0, sizeof(WIDTH));
	memset(&FULLWIDTH, 0, sizeof(FULLWIDTH));

	for(unsigned int i = 0; i < unit_data.size(); ++i)
	{
		WIDTH.name     = std::max(WIDTH.name    , unit_data[i].unit->_name_utf8_length);
		WIDTH.units    = std::max(WIDTH.units   , utils::num_width(unit_data[i].start_number));
		WIDTH.maxlost  = std::max(WIDTH.maxlost , utils::num_width(unit_data[i].start_number-unit_data[i].results.min));
	}


	FULLWIDTH.name     = std::max(WIDTH.name      , (int)std::string("Rounds").length());
	FULLWIDTH.minmax   = std::max(WIDTH.units*2+3 , (int)std::string("Rounds").length());
	FULLWIDTH.average  = std::max(WIDTH.units*2+4 , (int)std::string("average").length());

	out << "\n==============================================================================================\n";
	out << std::setw(FULLWIDTH.name+1) << "Units"
		<< " [" << std::setw(WIDTH.units) << multinum(WIDTH.units) << "] ";

	if(show_minmax) {
		out	<< " | " << std::setw(FULLWIDTH.minmax)  << "min";
		if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";
	}
		
	out << " | " << std::setw(FULLWIDTH.minmax)  << "smin";
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";

	out << " | " << std::setw(FULLWIDTH.average) << "average";
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+7) << "";

	out << " | " << std::setw(FULLWIDTH.minmax)  << "smax";
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";

	if(show_minmax) {
		out << " | " << std::setw(FULLWIDTH.minmax)  << "max";
		if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";
	}
	
	out << std::endl;

	out << "  --------------------------------------------------------------------------------------------\n";

	out << std::setw(FULLWIDTH.name+1)    << "Rounds" 
		<< std::setw(WIDTH.units+3)   << "" << " ";

	if(show_minmax) {
		out << " | " << std::setw(FULLWIDTH.minmax)  << rounds_data.results.min;
		if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";
	}

	out << " | " << std::setw(FULLWIDTH.minmax)  << rounds_data.results.stat_min;
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";

	out << " | " << std::setw(FULLWIDTH.average) << rounds_data.results.average;
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+7) << "";

	out << " | " << std::setw(FULLWIDTH.minmax)  << rounds_data.results.stat_max;
	if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";

	if(show_minmax) {
		out << " | " << std::setw(FULLWIDTH.minmax)  << rounds_data.results.max;
		if(WIDTH.maxlost > 0) out << std::setw(WIDTH.maxlost+4) << "";
	}
	
	out << std::endl;

	out << "  --------------------------------------------------------------------------------------------\n";

	for(unsigned int i = 0; i < unit_data.size(); ++i)
	{
		Data& data = unit_data[i];
		out << std::setw(FULLWIDTH.name+(data.unit->name.length()-data.unit->_name_utf8_length)+1) << data.unit->name
			<< " ["  << std::setw(WIDTH.units)       << data.start_number << "] ";

		if(show_minmax) {
			print_result_number(out, data.results.min     , data.start_number, FULLWIDTH.minmax, WIDTH.units, WIDTH.maxlost);
		}
		print_result_number(out, data.results.stat_min, data.start_number, FULLWIDTH.minmax, WIDTH.units, WIDTH.maxlost);

		print_result_number_fl(out, data.results.average, data.start_number, FULLWIDTH.minmax, WIDTH.units, WIDTH.maxlost);

		print_result_number(out, data.results.stat_max, data.start_number, FULLWIDTH.minmax, WIDTH.units, WIDTH.maxlost);
		if(show_minmax) {
			print_result_number(out, data.results.max     , data.start_number, FULLWIDTH.minmax, WIDTH.units, WIDTH.maxlost);
		}

		out << std::endl;
	}
	out << "==============================================================================================\n";
	out << std::endl;


	out << "\nHistogram (rounds):\n";
	{
		std::vector<int> Hist;

		int hmin = std::max(rounds_data.results.stat_min, rounds_data.results.min);
		int hmax = std::max(rounds_data.results.stat_max, rounds_data.results.max) + 1;

		Hist.resize(hmax);

		std::list<int>::iterator it = rounds_data.iteration_results.begin();
		for(; it != rounds_data.iteration_results.end(); ++it)
		{
			Hist[*it]++;
		}

		int max_length=80;
		for(int i=hmin; i<hmax; ++i)
		{
			out << std::setw(3) << i << " : ";

			out << std::setw(utils::num_width(rounds_data.number_iterations)) << Hist[i] << " ~ ";

			double percent = Hist[i]/(double)rounds_data.number_iterations;
			out.precision(2);
			out << std::setw(3+1+2) << std::fixed << (100*percent) << "% : ";

			double len = max_length * percent;
			for(int l=0; l < len; ++l)
				out << "*";

			double rest = len - (int)len;

			if(rest < 0.5)  {}
			//if(rest < 0.5) out << "-";
			else out << "+";

			//out << " (" << Hist[i] << ")";
			out << std::endl;
		}
	}
}
